// -*- c-basic-offset: 2 default-tab-width: 2 indent-tabs-mode: t -*-
// vim: autoindent tabstop=2 noexpandtab shiftwidth=2 softtabstop=2
#ifndef _CONVERT_H_
#define _CONVERT_H_

#include <stdlib.h>
#include <strings.h>

#include "Any.h"
#include "Const.h"

using namespace R;

template <typename S, typename T> inline
T convert(S val) { return T(val); }

///////////// toLogical
// In the very special case of toLogicalOne: this conversion may be:
// return (NA_INT == val) ? NA_LOG : val;
// thus make sense to rewrite all of these conversion
template <> inline
r_bool convert(r_int val) {
	return (ConstPool::NA_INT == val)
			? ConstPool::NA_LOG
			: val ? 1 : 0;
	// Prefer this way to val != 0 which is platform dependent,
	// generated code is the same on most (all ?) platform,
	// with gcc (all mode) and clang with -O (strangely not without).
}

template <> inline
r_bool convert(r_double val) {
	return (ConstPool::NA_DOUBLE == val)
		? ConstPool::NA_LOG
		: val ? 1 : 0;
}

template <> inline
r_bool convert(r_string val) {
	if(val == ConstPool::NA_STRING) // Deal with the common case first
		return ConstPool::NA_LOG;
	if(!strcmp(val, ConstPool::TRUE_STRING))
		return 1;
	if(!strcmp(val, ConstPool::FALSE_STRING))
		return 0;
	if(!strcmp(val, "T")) // Then some tolerated values
		return 1;
	if(!strcmp(val, "F"))
		return 0;
	warning("Couldn't convert string to logical, using NA!"); // Finally warn and continue
	return ConstPool::NA_LOG;
}

///////////// toInt
template <> inline
r_int convert(r_bool val) {
	return (ConstPool::NA_LOG == val)
			? ConstPool::NA_INT
			: int(val);
}

template <> inline
r_int convert(r_double val) {
	return (ConstPool::NA_DOUBLE == val)
		? ConstPool::NA_INT
		: int(val); 
}

template <> inline
r_int convert(r_string val) {
	// TODO check (for this need to reset before, but when ?) errno
	// to return NA + Warning, or maybe use the endptr
	if(ConstPool::NA_STRING == val) return ConstPool::NA_INT;
	if(!*val) return 0; /* and do a warning*/
	if(val[0] == '0'){
		if(val[1] == 'x')
			return strtol(val+2, NULL, 16);
		else
			return strtol(val+1, NULL, 8);
	}
	return strtol(val, NULL, 10);
	// check_error("Couldn't convert value to int, use NA!");
}

///////////// toDouble
template <> inline
r_double convert(r_bool val) {
	return (ConstPool::NA_LOG == val)
			? ConstPool::NA_DOUBLE
			: val;
}

template <> inline
r_double convert(r_int val) {
	return (ConstPool::NA_INT == val)
			? ConstPool::NA_DOUBLE
			: r_double(val);
}

template <> inline
r_double convert(r_string val) {
	// TODO check (for this need to reset before, but when ?) errno
	// to return NA + Warning, or maybe use the endptr
	return (ConstPool::NA_STRING == val)
			? ConstPool::NA_DOUBLE
			: strtod(val, NULL);
	// check_error("Couldn't convert value to int, use NA!");
}

///////////// toString
template <> inline
r_string convert(r_bool val) {
	return r_string((ConstPool::NA_LOG == val)
			 ? ConstPool::NA_STRING
			 : val
				? ConstPool::TRUE_STRING
				: ConstPool::FALSE_STRING);
	// FIXME remove this cast by either duplicating OR changing sig inside Array::String
}

#define MAX_NUMBER_LEN		1001

template <typename T>
char* to_string(T const& value, const char* format, int alloc) {
/*	FIXME this coudl be a good c++ way, but:
 *	- I dont know the overhead
 *	- I don't know which is the best way to get a char*
 *	- Finally, may have a lot of deps on stl
 *	stringstream sstr;
	sstr << value;
	return sstr.str();
	*/
	static char buff[MAX_NUMBER_LEN] = {};
	int len = snprintf(buff, MAX_NUMBER_LEN-1, format, value);
	if(alloc)
		return (char*)memcpy(new char[len + 1], buff, len + 1);
	return buff;
}

#define QUOTE_RELAY(t)		#t
#define QUOTE(t)			QUOTE_RELAY(t)

template <> inline
r_string convert(r_int val) {
	return (ConstPool::NA_INT == val)
			? (char *) ConstPool::NA_STRING // FIXME remove this cast by either duplicating OR changing sig inside Array::String
			: to_string(val, "%" QUOTE(MAX_NUMBER_LEN) "d", 1);
}

template <> inline
r_string convert(r_double val) {
	return (ConstPool::NA_DOUBLE == val)
			? (char *) ConstPool::NA_STRING // FIXME remove this cast by either duplicating OR changing sig inside Array::String
			: to_string(val,"%" QUOTE(MAX_NUMBER_LEN) "g", 1);
}
#undef MAX_NUMBER_LEN
#undef QUOTE_RELAY
#undef QUOTE

static inline r_bool R_AND(r_bool one, r_bool other) {
  if(!one)
	return one;
  else if(one == 1)
	return other;
  else if(!other)
	return other;
  else
	return ConstPool::NA_LOG;
}

#endif
